package udpSve;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

public class UDPServerNode extends AbstractNodeKomunikacija {

	private int requestID = 1;

	List<Entry<InetAddress, Integer>> sviServeri;
	volatile int brojNodova;
	volatile int brojNodovaKojeSamJaDodao;
	Map<Entry<InetAddress, Integer>, Integer> brojNodovaKojeSuDodali;
	int pocetakOpsega, krajOpsega, brojChunkova;

	ArrayList<Entry<ChunkState, Long>> chunkStates; // state i
													// timestamp(currentMillis)
	Map<Integer, List<Integer>> prostiPoChunku;

	Random myRandomizer = new Random();
	WorkerProstiBrojevi worker;
	boolean finished;

	private String progressInfo;
	private List<Entry<Double,Long>> lastPercentages;
	private boolean ostaloProstihDaSePrebace;

	public UDPServerNode(ArrayList<Entry<ChunkState, Long>> chunkStates,
			int requestID, String opseg, int listenPort,
			List<Entry<InetAddress, Integer>> sviServeri) {
		super(listenPort);
		this.requestID = requestID;
		this.sviServeri = sviServeri;
		this.brojNodovaKojeSuDodali = new ConcurrentHashMap<Entry<InetAddress, Integer>, Integer>();
		brojNodova = sviServeri.size();
		brojNodova++;
		brojNodovaKojeSamJaDodao = 0;
		String[] split = opseg.split(",");
		pocetakOpsega = Integer.parseInt(split[0]);
		krajOpsega = Integer.parseInt(split[1]);
		brojChunkova = (krajOpsega - pocetakOpsega) / Commmands.VELICINA_CHUNKA;
		if ((krajOpsega - pocetakOpsega) % Commmands.VELICINA_CHUNKA != 0)
			brojChunkova++;

		if (chunkStates == null) {
			this.chunkStates = new ArrayList<Entry<ChunkState, Long>>();
			for (int i = 0; i < brojChunkova; i++) {
				this.chunkStates
						.add(new AbstractMap.SimpleEntry<ChunkState, Long>(
								ChunkState.NOT_DONE, 0l)); // Zapravo ovo treba
															// da stigne zajedno
															// sa listom ostalih
															// servera.
			}
		} else {
			this.chunkStates = chunkStates;
		}
		worker = new WorkerProstiBrojevi();
		prostiPoChunku = new ConcurrentHashMap<Integer, List<Integer>>();
		finished = false;
		this.progressInfo = "Progress initializing.";
		this.lastPercentages = new LinkedList<Entry<Double,Long>>();
		this.ostaloProstihDaSePrebace = true;
		System.out.println("PRAVIM NOVI SERVER NODE");
	}

	public void start() {
		worker.start();
		startRecieving();
		javiSeSvimServerima();
		Thread periodicnoRadjenjeStvari = new Thread(new Runnable() {

			@Override
			public void run() {
				while (!finished) {
					// brodkastovanje server info
					/*
					 * for (Entry<InetAddress, Integer> entry : sviServeri) {
					 * sendPaket(entry.getKey(), entry.getValue(), requestID,
					 * Commmands.SERVER_INFO, Integer.toString(
					 * brojNodovaKojeSamJaDodao));//za sada samo broj koje sam
					 * doda, posle u telo i ostale informacije requestID++; }
					 */

					// provera da li radi poso
					if (!worker.isWorking() && !finished) {
						// proveriti dal je prethodni zavrsen i brodkastovati da
						// je zavrsen (Mozda zajedno i sa prostim brojevima)
						if (worker.getChunkId() != -1) {
							long timestamp = System.currentTimeMillis();
							chunkStates.set(
									worker.getChunkId(),
									new AbstractMap.SimpleEntry<ChunkState, Long>(
											ChunkState.DONE, timestamp));
							prostiPoChunku.put(worker.getChunkId(),
									worker.getProsti());

							StringBuilder sb = new StringBuilder();
							for (Integer prost : worker.getProsti()) {
								sb.append(";");
								sb.append(prost);
							}
							worker.setProsti(new ArrayList<Integer>());
							for (Entry<InetAddress, Integer> entry : sviServeri) {
								sendPaket(entry.getKey(), entry.getValue(),
										requestID,
										Commmands.SERVER_FINISHED_CHUNK,
										worker.getChunkId() + ";" + timestamp
												+ sb.toString()); // nveli:
																	// DODAO: +
																	// sb.toString()
								requestID++;
							}
							worker.setChunkId(-1);
						}

						// nadji chunkove koji ne rade, ili rade a proslo puno
						// od timestampa
						List<Integer> chunksToBeDone = new ArrayList<>();
						for (int i = 0; i < chunkStates.size(); i++) {
							Entry<ChunkState, Long> entry = chunkStates.get(i);
							if (entry.getKey().equals(ChunkState.NOT_DONE)) {
								chunksToBeDone.add(i);
							} else if (entry.getKey().equals(
									ChunkState.IN_PROGRESS)
									&& (System.currentTimeMillis() - entry
											.getValue()) > 600000) {
								chunksToBeDone.add(i);
							}
						}
						if (chunksToBeDone.size() == 0) {
							
							boolean daLiImaInProgress = false;
							for (int i = 0; i < chunkStates.size(); i++) {
								Entry<ChunkState, Long> entry = chunkStates.get(i);
								if (entry.getKey().equals(ChunkState.IN_PROGRESS)) {
									daLiImaInProgress = true;
									break;
								}
							}
							//Ako ih ima jos onda treba i dalje da proverava za slucaj da neki istekne
							if(!daLiImaInProgress){
								//System.out.println(prostiPoChunku);
								finished = true;
							}
							continue;
						}
						// zarandomaj koji ces
						Integer chunkToDo = chunksToBeDone.get(myRandomizer
								.nextInt(chunksToBeDone.size()));

						worker.setChunk(chunkToDo, (chunkToDo
								* Commmands.VELICINA_CHUNKA + 1),
								((chunkToDo + 1) * Commmands.VELICINA_CHUNKA));

						long timestamp = System.currentTimeMillis();
						chunkStates.set(chunkToDo,
								new AbstractMap.SimpleEntry<ChunkState, Long>(
										ChunkState.IN_PROGRESS, timestamp));
						// posalji svima da ga radis
						for (Entry<InetAddress, Integer> entry : sviServeri) {

							sendPaket(entry.getKey(), entry.getValue(),
									requestID, Commmands.SERVER_STARTED_CHUNK,
									chunkToDo + ";" + timestamp);
							requestID++;
						}

					}
					
					//Racunanje info
					if(!finished){
						int countDone=0;
						for (int i = 0; i < chunkStates.size(); i++) {
							Entry<ChunkState, Long> entry = chunkStates.get(i);
							if (entry.getKey().equals(ChunkState.DONE)) {
								countDone++;
							}
						}
						Double percentDone = (double)countDone/chunkStates.size();
						lastPercentages.add(new AbstractMap.SimpleEntry<Double,Long>(percentDone,System.currentTimeMillis()));
						if(lastPercentages.size()>10) lastPercentages.remove(0);//Samo poslednjih 10
						
						double speedSum = 0;
						double count = 0;
						for (int i = 0; i < lastPercentages.size()-1; i++) {
							Entry<Double, Long> entryTren = lastPercentages.get(i);
							Entry<Double, Long> entrySled = lastPercentages.get(i+1);
							double speed = (entrySled.getKey()-entryTren.getKey())/((entrySled.getValue()-entryTren.getValue())/1000.0);
							speedSum +=speed;
							count++;
						}
						double speedMean = speedSum/count; //u procentima po sekundi
						double secondsLeft = (1-percentDone)/speedMean;
						StringBuilder sb = new StringBuilder();
						sb.append("Current done: ").append(percentDone).append(" Current speed: ").append(speedMean).append(" Estimate seconds left: ").append(secondsLeft);
						progressInfo = sb.toString();
						System.out.println(progressInfo);
						
					}
					

					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				while(ostaloProstihDaSePrebace){
					for (Entry<InetAddress, Integer> entry : sviServeri) {
						for (int i = 0; i < brojChunkova; i++) {
							boolean sviTu=true;
							if(!prostiPoChunku.containsKey(i)){
								sviTu = false;
								
								sendPaket(entry.getKey(),
										entry.getValue(), requestID++,
										Commmands.GIVE_ME_PRIMES,
										Integer.toString(i));//Treba proveriti ako je bas taj puko!
								try {
									Thread.sleep(30);
								} catch (InterruptedException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
							}
							if(sviTu) ostaloProstihDaSePrebace = false;
							
						}
						
						if(!ostaloProstihDaSePrebace) break;
						
						try {
							Thread.sleep(500);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				System.out.println(prostiPoChunku);
			}
		});
		periodicnoRadjenjeStvari.start();
		while (true) {
			try {
				Thread.sleep(1000); // Ovde ce verovatno trebati da ceka da se
									// zavrsi ceo opseg svi prosti brojevi pa da
									// se ispise kad se zavrsi...
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	private void javiSeSvimServerima() {
		for (Entry<InetAddress, Integer> entry : sviServeri) {
			sendPaket(entry.getKey(), entry.getValue(), requestID,
					Commmands.SERVER_HELLO, "EMPTY");
			requestID++;
		}
	}

	@Override
	protected void obradiPaket(InetAddress recieveAddress, int recievePort,
			int requestID, String komanda, String telo) {
		if (komanda.equals(Commmands.NEW_NODE)) {
			StringBuilder sb = new StringBuilder();
			sb.append(";");
			sb.append(sviServeri.size() + 1);
			for (Entry<InetAddress, Integer> entry : sviServeri) {
				sb.append(";");
				sb.append(entry.getKey().getHostAddress());
				sb.append(";");
				sb.append(entry.getValue());
			}
			sb.append(";");
			try {
				sb.append(InetAddress.getLocalHost().getHostAddress());
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
			sb.append(";");
			sb.append(listenPort);

			String role;
			if (Math.log((double) brojNodova) > sviServeri.size()) {
				role = Commmands.ROLE_SERVER;
			} else {
				brojNodovaKojeSamJaDodao++;
				brojNodova++;
				role = Commmands.ROLE_CLIENT;
			}

			StringBuilder chunksString = new StringBuilder();
			chunksString.append(";");
			chunksString.append(chunkStates.size());

			for (Entry<ChunkState, Long> entry : chunkStates) {
				chunksString.append(";");
				chunksString.append(entry.getKey().name());
				chunksString.append(";");
				chunksString.append(entry.getValue());
			}

			sendPaket(recieveAddress, recievePort, requestID,
					Commmands.NEW_NODE_RESPONSE, role + sb.toString()
							+ chunksString.toString());

		} else if (komanda.equals(Commmands.SERVER_INFO)) {
			Entry<InetAddress, Integer> entry = new AbstractMap.SimpleEntry<InetAddress, Integer>(
					recieveAddress, recievePort);
			if (!sviServeri.contains(entry)) {
				sviServeri.add(entry);
			}
			int broj = Integer.parseInt(telo);// za sada zamo broj, posle mozda
												// jos
			brojNodovaKojeSuDodali.put(entry, broj);
			brojNodova = 0;
			for (Integer brojevi : brojNodovaKojeSuDodali.values()) {
				brojNodova += brojevi;
			}
			brojNodova += brojNodovaKojeSamJaDodao;
			brojNodova += sviServeri.size()+1;// DA LI OVDE TREBA +1 ZA SEBE?
											// nveli TREBA djole
		} else if (komanda.equals(Commmands.SERVER_HELLO)) { // Kad se napravi
																// novi server
																// pa mu se javi
			Entry<InetAddress, Integer> entry = new AbstractMap.SimpleEntry<InetAddress, Integer>(
					recieveAddress, recievePort);
			if (!sviServeri.contains(entry)) {
				sviServeri.add(entry);
			}
		} else if (komanda.equals(Commmands.SERVER_STARTED_CHUNK)) {
			String[] split = telo.split(";");
			int chunkId = Integer.parseInt(split[0]);
			long timestamp = Long.parseLong(split[1]);
			
			chunkStates.set(chunkId,
					new AbstractMap.SimpleEntry<ChunkState, Long>(
							ChunkState.IN_PROGRESS, timestamp));

		} else if (komanda.equals(Commmands.CLIENT_STARTED_CHUNK)) {
			String[] split = telo.split(";");
			int chunkId = Integer.parseInt(split[0]);
			long timestamp = Long.parseLong(split[1]);

			chunkStates.set(chunkId,
					new AbstractMap.SimpleEntry<ChunkState, Long>(
							ChunkState.IN_PROGRESS, timestamp));
			// SADA OVAJ SERVER MORA SVIM SERVERIMA DA PROSLEDI OVO...

			for (Entry<InetAddress, Integer> entry : sviServeri) {

				sendPaket(entry.getKey(), entry.getValue(), this.requestID,
						Commmands.SERVER_STARTED_CHUNK, chunkId + ";"
								+ timestamp); // STAVILI SMO
												// SERVER_STARTED_CHUNK JER
												// NISMO HTELI DA DODAJEMO NOVU
												// KOMANDU nveli (RADICE!!!)
				this.requestID++;
			}

		} else if (komanda.equals(Commmands.SERVER_FINISHED_CHUNK)) {
			String[] split = telo.split(";");
			int chunkId = Integer.parseInt(split[0]);
			long timestamp = Long.parseLong(split[1]);

			ArrayList<Integer> prosti = new ArrayList<Integer>();
			for (int i = 2; i < split.length; i++) {
				int prost = Integer.parseInt(split[i]);
				prosti.add(prost);
			}

			chunkStates.set(chunkId,
					new AbstractMap.SimpleEntry<ChunkState, Long>(
							ChunkState.DONE, timestamp));
			prostiPoChunku.put(chunkId, prosti);
		} else if (komanda.equals(Commmands.CLIENT_FINISHED_CHUNK)) {
			String[] split = telo.split(";");
			int chunkId = Integer.parseInt(split[0]);
			long timestamp = Long.parseLong(split[1]);

			ArrayList<Integer> prosti = new ArrayList<Integer>();
			for (int i = 2; i < split.length; i++) {
				int prost = Integer.parseInt(split[i]);
				prosti.add(prost);
			}

			chunkStates.set(chunkId,
					new AbstractMap.SimpleEntry<ChunkState, Long>(
							ChunkState.DONE, timestamp));
			prostiPoChunku.put(chunkId, prosti);

			// SADA OVAJ PROSLEDJUJE SVIM SERVERIMA DA JE KLIJENT ZAVRSIO CHUNK
			StringBuilder sb = new StringBuilder();
			for (Integer prost : prosti) {
				sb.append(";");
				sb.append(prost);
			}
			for (Entry<InetAddress, Integer> entry : sviServeri) {
				sendPaket(entry.getKey(), entry.getValue(), this.requestID,
						Commmands.SERVER_FINISHED_CHUNK, chunkId + ";"
								+ timestamp + sb.toString()); // ISTO KAO I ZA
																// CLIENT_STARTED_CHUNK...
																// away nvelii
				this.requestID++;
			}
		} else if (komanda.equals(Commmands.GIVE_ME_NEW_CHUNK)) {
			//ukoliko je finished, samo treba da se posalje komanda da je sve zavrseno
			if(finished){
				sendPaket(recieveAddress, recievePort, requestID,
						Commmands.ALL_FINISHED, "EMPTY");
			}
			else{	
				
				// POSLATI CHUNK KLIJENTU
				List<Integer> chunksToBeDone = new ArrayList<>();
				for (int i = 0; i < chunkStates.size(); i++) {
					Entry<ChunkState, Long> entry = chunkStates.get(i);
					if (entry.getKey().equals(ChunkState.NOT_DONE)) {
						chunksToBeDone.add(i);
					} else if (entry.getKey().equals(ChunkState.IN_PROGRESS)
							&& (System.currentTimeMillis() - entry.getValue()) > 600000) {
						chunksToBeDone.add(i);
					}
				}
				if (chunksToBeDone.size() == 0) {
					System.out.println(prostiPoChunku);
					// POLSLATI MU DA NEMA VISE
					sendPaket(recieveAddress, recievePort, requestID,
							Commmands.HERE_IS_A_CHUNK, String.valueOf(-1));
					// POSLATI SVIM KLIJENTIMA prostiPoChunku DA BI ODSTAMPALI. Djole: Treba da posalje samo onom ko je pitao, ne zna za sve klijente, svi ce eventualno pitati i zavrsice se jednom.
				} else {
					// zarandomaj koji ces
	
					Integer chunkToDo = chunksToBeDone.get(myRandomizer
							.nextInt(chunksToBeDone.size()));
					// POSLATI MU DA RADI TAJ
					sendPaket(recieveAddress, recievePort, requestID,
							Commmands.HERE_IS_A_CHUNK, String.valueOf(chunkToDo));
				}
			}
		}else if (komanda.equals(Commmands.GIVE_ME_NEW_INFO)) {
			
			//Kopiran kod za pravljenje teksta servera, mozda da stavimo u metodu neku.
			StringBuilder sb = new StringBuilder();
			sb.append(";");
			sb.append(sviServeri.size() + 1);
			for (Entry<InetAddress, Integer> entry : sviServeri) {
				sb.append(";");
				sb.append(entry.getKey().getHostAddress());
				sb.append(";");
				sb.append(entry.getValue());
			}
			sb.append(";");
			try {
				sb.append(InetAddress.getLocalHost().getHostAddress());
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
			sb.append(";");
			sb.append(listenPort);
			
			sendPaket(recieveAddress, recievePort, requestID,
					Commmands.HERE_IS_INFO, progressInfo+sb.toString());
		}else if (komanda.equals(Commmands.GIVE_ME_PRIMES)) {
			int chunkId = Integer.valueOf(telo);
			List<Integer> prosti = prostiPoChunku.get(chunkId);
			if(prosti!=null){//Ako nemas ove proste, samo ne odgovaraj, odgovorice vec neko.
				StringBuilder sb = new StringBuilder();
				for (Integer prost : prosti) {
					sb.append(";");
					sb.append(prost);
				}
				
				sendPaket(recieveAddress, recievePort, requestID,
						Commmands.HERE_ARE_PRIMES, String.valueOf(chunkId+sb.toString()));
				//Telo: chunkId;prost;prost;prost
			}
		}
	}

}
